package svr

import (
	"errors"
	"fmt"
	"strings"

	"github.com/gogf/gf/container/gmap"
	"github.com/gogf/gf/net/gtcp"
)

var (
	NewServerService = new(serviceServer)
	clientList       = gmap.New(true) //map[clientId string]conn *gtcp.Conn
	client2UidList   = gmap.New(true) //map[clientId string]uid string
	uid2ClientList   = gmap.New(true) //map[uid string]clientId string
)

type serviceServer struct {
	address        string
	serverName     string
	messageHandler func(conn *gtcp.Conn, message []byte)
}

func (s *serviceServer) NewServer() error {
	if len(s.address) == 0 || len(s.serverName) == 0 || s.messageHandler == nil {
		return errors.New("please set address/serverName/messageHandler")
	}
	_ = gtcp.NewServer(s.address, s.OnNewServer, s.serverName).Run()
	return nil
}

func (s *serviceServer) CloseConn(conn *gtcp.Conn) {
	s.onClose(conn)
	conn.Close()
}

func (s *serviceServer) BindUid(conn *gtcp.Conn, uid string) error {
	clientId, err := s.GetClientIdByConn(conn)
	if err != nil {
		return err
	}
	uid2ClientList.Set(uid, clientId)
	client2UidList.Set(clientId, uid)
	return nil
}

func (s *serviceServer) GetClientIdByConn(conn *gtcp.Conn) (string, error) {
	if conn == nil {
		return "", errors.New("conn is nil")
	}
	network := conn.RemoteAddr().Network()
	remoteAddr := conn.RemoteAddr().String()
	if len(network) == 0 || len(remoteAddr) == 0 {
		return "", errors.New("conn.(network/remote addr) is empty")
	}
	return fmt.Sprintf("client:%v:%v", network, remoteAddr), nil
}

func (s *serviceServer) GetUidByClientId(clientId string) string {
	return InterfaceToString(client2UidList.Get(clientId))
}

func (s *serviceServer) GetClientIdByUid(uid string) string {
	return InterfaceToString(uid2ClientList.Get(uid))
}

func (s *serviceServer) GetConnByUid(uid string) *gtcp.Conn {
	clientId := s.GetClientIdByUid(uid)
	if len(clientId) == 0 {
		return nil
	}
	return s.GetConnByClientId(clientId)
}

func (s *serviceServer) GetConnByClientId(clientId string) *gtcp.Conn {
	conn := clientList.Get(clientId)
	if conn == nil {
		return nil
	}
	return conn.(*gtcp.Conn)
}

func (s *serviceServer) SetServerAddress(address string) {
	s.address = address
}

func (s *serviceServer) SetServerName(serverName string) {
	s.serverName = serverName
}

func (s *serviceServer) SetMessageHandler(handler func(conn *gtcp.Conn, message []byte)) {
	s.messageHandler = handler
}

func (s *serviceServer) GetServerAddress() string {
	return s.address
}

func (s *serviceServer) GetServerName() string {
	return s.serverName
}

func (s *serviceServer) SendToCliendId(clientId string, data []byte) error {
	conn := s.GetConnByClientId(clientId)
	if conn == nil {
		return errors.New("conn is empty")
	}
	return conn.Send(data)
}

func (s *serviceServer) SendToUid(uid string, data []byte) error {
	conn := s.GetConnByUid(uid)
	if conn == nil {
		return errors.New("conn is empty")
	}
	return conn.Send(data)
}

func (s *serviceServer) OnNewServer(conn *gtcp.Conn) {
	defer s.CloseConn(conn)

	err := s.onConnection(conn)
	if err != nil {
		fmt.Println(err.Error())
		s.CloseConn(conn)
	}

	var clientId string

	for {
		data, err := conn.Recv(-1)
		if len(data) > 0 {
			if s.messageHandler != nil {
				s.messageHandler(conn, data)
			} else {
				clientId, _ = s.GetClientIdByConn(conn)
				fmt.Printf("rece data from [%v],data = %v\n", clientId, string(data))
			}
		}

		if err != nil {
			clientId, _ = s.GetClientIdByConn(conn)
			fmt.Println("recv-client-data-error", clientId, err)
			if err.Error() == "EOF" || (strings.Contains(err.Error(), "connection") && strings.Contains(err.Error(), "reset")) {
				fmt.Println("client closed", clientId)
			}
			s.CloseConn(conn)
			break
		}
	}
}

func (s *serviceServer) onConnection(conn *gtcp.Conn) error {
	clientId, err := s.GetClientIdByConn(conn)
	if err != nil {
		return err
	}
	clientList.Set(clientId, conn)
	return nil
}

func (s *serviceServer) onClose(conn *gtcp.Conn) error {
	clientId, err := s.GetClientIdByConn(conn)
	if err != nil {
		return err
	}
	uid := s.GetUidByClientId(clientId)
	if len(uid) > 0 {
		uid2ClientList.Remove(uid)
	}
	client2UidList.Remove(clientId)
	clientList.Remove(clientId)
	return nil
}
